//
// Created by xuyf7 on 2021/9/16.
//

#include "NDKPlayer.h"
#include "AudioChannel.h"
#include "VideoChannel.h"

NDKPlayer::NDKPlayer(const char *data_source_,JNICallbackHelp * jniCallbackHelp) {
//    this->data_source = data_source; 这样写会有问题,被释放后会变成悬空指针
    this->data_source = new char[strlen(data_source_) + 1];
    strcpy(this->data_source, data_source_);
    this->jniCallbackHel = jniCallbackHelp;
}

NDKPlayer::~NDKPlayer() {
    if (data_source) {
        delete data_source;
    }
}

void *task_prepare(void *args) {

    NDKPlayer *ndkPlayer = static_cast<NDKPlayer *>(args);
    ndkPlayer->prepare_();


    return 0;//必须返回.否则错误很难定位
}

void NDKPlayer::prepare_() {//此函数在子线程用来打开多媒体文件


    /**
     *
     * TODO 第一步
     *
     * AVFormatContext **ps, const char *url, AVInputFormat *fmt, AVDictionary **options
     * AVFormatContext **ps->上下文  (FFmpeg 上下文作用由来 : 因为我们在java开发中都会有一个对象的感念,
     * 但是FFmpeg是纯c开发的,没有对象概念所以就衍生出一个指针上下文来代替对象的作用)
     *
     * const char *url ->(文件地址)
     *
     * AVInputFormat *fmt ->(作用打开PC的摄像头和麦克风)
     *
     * AVDictionary **options->(超时时间)
     *
     */
    formatContext = avformat_alloc_context();

    AVDictionary *dictionary = 0;
    av_dict_set(&dictionary, "timeout", "500000", 0);
    int res = avformat_open_input(&formatContext, data_source, 0, &dictionary);

    //释放字典
    av_dict_free(&dictionary);
    if (res) {//把错误信息反馈给java.
        //TODO JNI反射给java
        return;
    }

    /**
     * TODO 第二步
     * 查找媒体中的音视频流信息
     */

    res = avformat_find_stream_info(formatContext, 0);

    if (res < 0) {

        //TODO JNI反射给java
        return;
    }

    /**
     * TODO第三步 根据流信息,流的个数,用循环找
     */
    int i = 0;
    for (; i < formatContext->nb_streams; ++i) {
        // TODO 第四步:获取媒体流(音频 视频)
        AVStream *avStream = formatContext->streams[i];
        //TODO 第五步 从上面的流中获取编码解码的参数
        AVCodecParameters *avCodecParameters = avStream->codecpar;
        //TODO 第六步 :获取解码器(根据上面的参数获取解码器)
        AVCodec *avCodec = avcodec_find_decoder(avCodecParameters->codec_id);
        //TODO 第七步 编解码器上下文(这个才是真正干活的)
        AVCodecContext *codecContext = avcodec_alloc_context3(avCodec);
        if (!codecContext) {//失败了
            //TODO JNI反射给java
            return;
        }
        //TODO 第八步 他目前是一张白纸(parameters copy codecContext)
        res = avcodec_parameters_to_context(codecContext, avCodecParameters);
        if (res < 0) {
            //TODO JNI反射给java
            return;
        }
        //TODO 第九步 打开解码器
        res = avcodec_open2(codecContext, avCodec, 0);

        if (res != 0) {
            //TODO JNI反射给java
            return;
        }
        //TODO 第十步:从编解码器参数中,获取流的类型 code_type == 视频或音频

        if (avCodecParameters->codec_type == AVMediaType::AVMEDIA_TYPE_VIDEO){//视频
            video_channel = new VideoChannel();

        } else if (avCodecParameters->codec_type == AVMediaType::AVMEDIA_TYPE_AUDIO){//音频
            audio_channel = new AudioChannel();

        }

    }//for end

    /**
     * TODO 第十一步 健壮性校验
     */
     if (!audio_channel && !video_channel){
         //TODO JNI反射给java
         return;
     }

     if (jniCallbackHel){
        jniCallbackHel->onPrepared(THREAD_CHILD);
     }

}

void NDKPlayer::prepare() {
    //问题1当前的prepare函数是子线程还是主线程 ?/我的答案是子线程.其实我答错了,他是主线程.因为它是在onResume中调用的
    //问题2我们要把视频压缩文件解压缩.它是一种耗时操作,
    // 可以直接解析吗?我的答案肯定不可以了,请使用子线程.
    // 不管他是网络直播流还是文件io操作都是耗时操作必须使用子线程
    pthread_create(&pid_prepare, 0, task_prepare, this);

}


